﻿using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.Security.Principal;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections;
using System.Reflection;

namespace adolsignpreview
{
    class Program
    {
        enum ProgramCommand { ProgNone, ProgHelp, ProgSource, ProgDestination, ProgAdolsignConfig, ProgShowEnvironment, cmdSilent };

        static ProgramCommand cmdContext = ProgramCommand.ProgNone;

        static bool cmdHelp = false;

        static bool cmdSource = false;
        static string srcFolder = ".";

        static bool cmdDestination = false;
        static string dstFolder = "signatures";

        const string sectionFieldMappings = "Field Mappings";
        static bool cmdAdolsignConfig = false;
        static string cfgAdolsign = "adolsign.config";
        static IniParser iniParser;
        static string[] fieldMappings;

        static bool cmdShowEnvironment = false;

        static bool cmdSilent = false;

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                ConsoleWriteHelp();
                return;
            }

            foreach (string arg in args)
            {
                string a = arg.ToLowerInvariant();

                switch (a)
                {
                    case "--help":
                    case "/?":
                        cmdContext = ProgramCommand.ProgHelp;
                        cmdHelp = true;
                        break;

                    case "--source":
                    case "--src":
                        cmdContext = ProgramCommand.ProgSource;
                        cmdSource = true;
                        break;

                    case "--destination":
                    case "--dest":
                        cmdContext = ProgramCommand.ProgDestination;
                        cmdDestination = true;
                        break;

                    case "--adolsignconfig":
                    case "--cfg":
                        cmdContext = ProgramCommand.ProgAdolsignConfig;
                        cmdAdolsignConfig = true;
                        break;

                    case "--showenv":
                        cmdContext = ProgramCommand.ProgShowEnvironment;
                        cmdShowEnvironment = true;
                        break;

                    case "--silent":
                        cmdContext = ProgramCommand.cmdSilent;
                        cmdSilent = true;
                        break;

                    default:
                        switch (cmdContext)
                        {
                            case ProgramCommand.ProgHelp:
                                break;

                            case ProgramCommand.ProgSource:
                                srcFolder = a;
                                break;

                            case ProgramCommand.ProgDestination:
                                dstFolder = a;
                                break;

                            case ProgramCommand.ProgAdolsignConfig:
                                cfgAdolsign = a;
                                break;

                            case ProgramCommand.ProgShowEnvironment:
                                break;
                        }

                        cmdContext = ProgramCommand.ProgNone;
                        break;
                }
            }

            if (cmdAdolsignConfig && File.Exists(cfgAdolsign))
            {
                //using (StreamReader reader = new StreamReader(cfgAdolsign))
                //{
                //    string line = reader.ReadLine();
                //}

                iniParser = new IniParser(cfgAdolsign);
                fieldMappings = iniParser.EnumSection(sectionFieldMappings);
            }

            string defaultNamingContext;
            using (DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE"))
            {
                defaultNamingContext = rootDSE.Properties["defaultNamingContext"].Value.ToString();
                if(cmdShowEnvironment) ListThePropertiesOfRootDSE(rootDSE);
            }
            //Console.WriteLine("Accessing domain: {0}", defaultNamingContext);

            //string path = "LDAP://CN=Users,DC=corp,DC=home91,DC=com";
            //string path = "LDAP://RootDSE";
            string path = "LDAP://CN=Users," + defaultNamingContext;
            //string filter = "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))";
            //string[] propertiesToLoad = new string[1] { "name" };

            using (DirectoryEntry root = new DirectoryEntry(path))//, "xx\\xxxx", "xxxx"))
            //using (DirectorySearcher searcher = new DirectorySearcher(root, filter, propertiesToLoad))
            //using (SearchResultCollection results = searcher.FindAll())
            {
                SearchResult result = GetCurrentActiveDirectoryUser(root);

                //foreach (SearchResult result in results)
                //{
                //    string name = (string)result.Properties["name"][0];
                //    //ADUsersList.Items.Add(name);
                //    Console.WriteLine(name);
                //}

                if (!Directory.Exists(dstFolder)) Directory.CreateDirectory(dstFolder);

                string[] fileInfos = Directory.GetFiles(srcFolder);// ".");
                if (!cmdSilent) Console.WriteLine("\r\nPROCESSING FILES");

                foreach (string s in fileInfos)
                {
                    string d = s.Replace(srcFolder, dstFolder);
                    
                    if (!cmdSilent)
                    {
                        Console.WriteLine(s + " to " + d);
                    }

                    using (StreamReader reader = new StreamReader(s))//, Encoding.UTF8))//;
                    using (StreamWriter writer = new StreamWriter(d))//, false, reader.CurrentEncoding))// Encoding.UTF8))
                    {
                        string template = reader.ReadToEnd();
                        //template.Replace(
                        Regex regex = new Regex("\\w+");
                        MatchCollection words = regex.Matches(template);
                        string[] btw = regex.Split(template);

                        //foreach (string space in btw)
                        for (int idx = 0; idx < words.Count; idx++)
                        {
                            writer.Write(btw[idx]);
                            string w = words[idx].Value.ToLowerInvariant();

                            if (cmdAdolsignConfig)
                            {
                                string w1 = iniParser.GetSetting(sectionFieldMappings, w);

                                if (w1 == null)
                                {
                                    writer.Write(words[idx].Value);
                                    continue;
                                }

                                ResultPropertyValueCollection res = result.Properties[w1];
                                if (res.Count > 0)
                                {
                                    writer.Write(res[0]);
                                }
                                else
                                {
                                    //writer.Write(words[idx].Value);
                                }
                            }
                            else
                            {
                                writer.Write(words[idx].Value);
                            }
                        }
                        writer.Write(btw[words.Count]);
                    }
                }
            }

            if (!cmdSilent)
            {
                Console.WriteLine("\r\nConverted successfully");
                Console.WriteLine("Press ENTER for exit...");
                Console.ReadLine();
            }
        }

        private static void ConsoleWriteHelp()
        {
            Console.WriteLine("Adolsign Preview " + Assembly.GetExecutingAssembly().GetName().Version.ToString());
            Console.WriteLine("usage: adolsignpreview --src folder1 --dst folder2 --cfg configfile");
            Console.WriteLine("additional:");
            Console.WriteLine("--showenv");
            Console.WriteLine("--silent");
        }

        static void ListThePropertiesOfRootDSE(DirectoryEntry rootDSE)
        {
            //using (DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE"))
            //{
                Console.WriteLine("\r\nLIST THE PROPERTIES OF RootDSE");
                Console.WriteLine("\r\nAttributes with a single value:");
                foreach (string propertyName in rootDSE.Properties.PropertyNames)
                {
                    if (rootDSE.Properties[propertyName].Count == 1)
                    {
                        //Console.WriteLine("{0,30} = {1}", propertyName,
                        Console.WriteLine("{0} =\r\n    {1}", propertyName,
                            rootDSE.Properties[propertyName].Value);
                        continue;
                    }
                }
                Console.WriteLine("\r\nAttributes with multiple values:");
                foreach (string propertyName in rootDSE.Properties.PropertyNames)
                {
                    if (rootDSE.Properties[propertyName].Count > 1)
                    {
                        Console.WriteLine("    {0}:", propertyName);
                        foreach (object obj in (object[])(rootDSE.Properties[propertyName].Value))
                        {
                            Console.WriteLine("        {0}", obj.ToString());
                        }
                    }
                }
            //}
        }

        static SearchResult GetCurrentActiveDirectoryUser(DirectoryEntry root)
        {
            DirectorySearcher ds = new DirectorySearcher(root);
            WindowsIdentity winIdent = WindowsIdentity.GetCurrent();
            string userName = winIdent.Name;
            int hasDomain = userName.IndexOf(@"\");
            if (hasDomain > 0)
            {
                userName = userName.Remove(0, hasDomain + 1);
            }

            //string userFilter = "(&(objectCategory=user)(sAMAccountName={0}))";
            string userFilter = "(&(objectClass=user)(sAMAccountName={0}))";

            ds.SearchScope = SearchScope.Subtree;
            //ds.PropertiesToLoad.Add("distinguishedName");
            //ds.PageSize = 1;
            //ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2);
            ds.Filter = string.Format(userFilter, userName);

            SearchResult sr = ds.FindOne();
            // now do something with sr.Properties["distinguishedName"][0]        

            if (cmdShowEnvironment)
            {
                Console.WriteLine("\r\nCURRENT ActiveDirectory USER PROPERTIES");
                foreach (string myKey in sr.Properties.PropertyNames)
                {
                    string tab = "    ";
                    Console.WriteLine(myKey + " = ");
                    foreach (Object myCollection in sr.Properties[myKey])
                    {
                        Console.WriteLine(tab + myCollection);
                    }//end foreach
                } //end foreach
            }

            ds.Dispose();
            return sr;
        }
    }

    /// <summary>
    /// based on http://bytes.com/topic/net/insights/797169-reading-parsing-ini-file-c
    /// </summary>
    public class IniParser
    {
        private Hashtable keyPairs = new Hashtable();
        private String iniFilePath;

        private struct SectionPair
        {
            public String Section;
            public String Key;
        }

        /// <summary>
        /// Opens the INI file at the given path and enumerates the values in the IniParser.
        /// </summary>
        /// <param name="iniPath">Full path to INI file.</param>
        public IniParser(String iniPath)
        {
            TextReader iniFile = null;
            String strLine = null;
            String currentRoot = null;
            String[] keyPair = null;

            iniFilePath = iniPath;

            if (File.Exists(iniPath))
            {
                try
                {
                    iniFile = new StreamReader(iniPath);

                    strLine = iniFile.ReadLine();

                    while (strLine != null)
                    {
                        strLine = strLine.Trim().ToLowerInvariant();//.ToUpper();

                        if (strLine != "")
                        {
                            if (strLine.StartsWith("[") && strLine.EndsWith("]"))
                            {
                                currentRoot = strLine.Substring(1, strLine.Length - 2);
                            }
                            else
                            {
                                if (strLine.StartsWith("'") || strLine.StartsWith("#"))
                                {
                                    // assuming comments start with the apostrophe
                                    // do nothing
                                }
                                else
                                {
                                    keyPair = strLine.Split(new char[] { '=' }, 2);

                                    SectionPair sectionPair;
                                    String value = null;

                                    if (currentRoot == null)
                                        currentRoot = "ROOT";

                                    sectionPair.Section = currentRoot;
                                    sectionPair.Key = keyPair[0].Trim();

                                    if (keyPair.Length > 1)
                                        value = keyPair[1].Trim();

                                    keyPairs.Add(sectionPair, value);
                                }
                            }
                        }

                        strLine = iniFile.ReadLine();
                    }

                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    if (iniFile != null)
                        iniFile.Close();
                }
            }
            else
                throw new FileNotFoundException("Unable to locate " + iniPath);

        }

        /// <summary>
        /// Returns the value for the given section, key pair.
        /// </summary>
        /// <param name="sectionName">Section name.</param>
        /// <param name="settingName">Key name.</param>
        public String GetSetting(String sectionName, String settingName)
        {
            SectionPair sectionPair;
            sectionPair.Section = sectionName.ToLowerInvariant();//.ToUpper();
            sectionPair.Key = settingName.ToLowerInvariant();//.ToUpper();

            return (String)keyPairs[sectionPair];
        }

        /// <summary>
        /// Enumerates all lines for given section.
        /// </summary>
        /// <param name="sectionName">Section to enum.</param>
        public String[] EnumSection(String sectionName)
        {
            ArrayList tmpArray = new ArrayList();

            foreach (SectionPair pair in keyPairs.Keys)
            {
                if (pair.Section == sectionName.ToLowerInvariant())//.ToUpper())
                    tmpArray.Add(pair.Key);
            }

            return (String[])tmpArray.ToArray(typeof(String));
        }

        /// <summary>
        /// Adds or replaces a setting to the table to be saved.
        /// </summary>
        /// <param name="sectionName">Section to add under.</param>
        /// <param name="settingName">Key name to add.</param>
        /// <param name="settingValue">Value of key.</param>
        public void AddSetting(String sectionName, String settingName, String settingValue)
        {
            SectionPair sectionPair;
            sectionPair.Section = sectionName.ToUpper();
            sectionPair.Key = settingName.ToUpper();

            if (keyPairs.ContainsKey(sectionPair))
                keyPairs.Remove(sectionPair);

            keyPairs.Add(sectionPair, settingValue);
        }

        /// <summary>
        /// Adds or replaces a setting to the table to be saved with a null value.
        /// </summary>
        /// <param name="sectionName">Section to add under.</param>
        /// <param name="settingName">Key name to add.</param>
        public void AddSetting(String sectionName, String settingName)
        {
            AddSetting(sectionName, settingName, null);
        }

        /// <summary>
        /// Remove a setting.
        /// </summary>
        /// <param name="sectionName">Section to add under.</param>
        /// <param name="settingName">Key name to add.</param>
        public void DeleteSetting(String sectionName, String settingName)
        {
            SectionPair sectionPair;
            sectionPair.Section = sectionName.ToUpper();
            sectionPair.Key = settingName.ToUpper();

            if (keyPairs.ContainsKey(sectionPair))
                keyPairs.Remove(sectionPair);
        }

        /// <summary>
        /// Save settings to new file.
        /// </summary>
        /// <param name="newFilePath">New file path.</param>
        public void SaveSettings(String newFilePath)
        {
            ArrayList sections = new ArrayList();
            String tmpValue = "";
            String strToSave = "";

            foreach (SectionPair sectionPair in keyPairs.Keys)
            {
                if (!sections.Contains(sectionPair.Section))
                    sections.Add(sectionPair.Section);
            }

            foreach (String section in sections)
            {
                strToSave += ("[" + section + "]\r\n");

                foreach (SectionPair sectionPair in keyPairs.Keys)
                {
                    if (sectionPair.Section == section)
                    {
                        tmpValue = (String)keyPairs[sectionPair];

                        if (tmpValue != null)
                            tmpValue = "=" + tmpValue;

                        strToSave += (sectionPair.Key + tmpValue + "\r\n");
                    }
                }

                strToSave += "\r\n";
            }

            try
            {
                TextWriter tw = new StreamWriter(newFilePath);
                tw.Write(strToSave);
                tw.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Save settings back to ini file.
        /// </summary>
        public void SaveSettings()
        {
            SaveSettings(iniFilePath);
        }
    }
}
